#include "Model.h"

#include "../logger/ScopedWriter.h"

#define _(x) QString::fromUtf8(x)

#include <iostream>
#include <QFileInfo>

namespace Client {

/*
	SharedFilesModel
*/

SharedFilesModel::SharedFilesModel(QList<SharedFileDesc*> & ref, QObject * parent) : QAbstractItemModel(parent), shared_(ref) {

}

int SharedFilesModel::rowCount(const QModelIndex & parent) const {
	Q_UNUSED(parent);
	return shared_.size();
}

int SharedFilesModel::columnCount(const QModelIndex & parent) const {
	Q_UNUSED(parent);
	// nazwa, typ, rozmiar, ilość pobrań, hash
	return 5;
}

QVariant SharedFilesModel::data(const QModelIndex & index, int role) const {
	SharedFileDesc * file = static_cast<SharedFileDesc*>(index.internalPointer());

	if (role != Qt::DisplayRole)
		 return QVariant();

	switch(index.column()) {
		case 0:
			return file->fname;
		case 1:
			return (int)file->type;
		case 2:
			return file->size;
		case 3:
			return 0;
		case 4:
			return file->hash.toHex();
		default:
			return QString("Hmmm");
	}
}

QModelIndex SharedFilesModel::index(int row, int column, const QModelIndex &parent) const
{
	if (!hasIndex(row, column, parent))
		return QModelIndex();

	 if (parent.isValid())
		 return QModelIndex();

	SharedFileDesc * file = shared_[row];

	return createIndex(row, column, file);
}

QModelIndex SharedFilesModel::parent(const QModelIndex &index) const {
	Q_UNUSED(index);
	return QModelIndex();
}

Qt::ItemFlags SharedFilesModel::flags(const QModelIndex &index) const
{
	if (!index.isValid())
		return 0;

	return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QVariant SharedFilesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
	if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
		switch (section) {
			case 0:
				return _("Nazwa pliku");
			case 1:
				return _("Typ");
			case 2:
				return _("Rozmiar");
			case 3:
				return _("Ilość pobrań");
			case 4:
				return _("MD5");
			default:
				return "";
		}
	}

	return QVariant();
}


/*
	JobsModel
*/

JobsModel::JobsModel(QList<Job*> & ref, QObject * parent) : QAbstractItemModel(parent), jobs_(ref) {

}

int JobsModel::rowCount(const QModelIndex & parent) const {
	Q_UNUSED(parent);
	return jobs_.size();
}

int JobsModel::columnCount(const QModelIndex & parent) const {
	Q_UNUSED(parent);
	// nazwa, typ, rozmiar, dawcy, postęp, hash
	return 6;
}

QVariant JobsModel::data(const QModelIndex & index, int role) const {
	Job * job = static_cast<Job*>(index.internalPointer());

	if (role != Qt::DisplayRole)
		 return QVariant();

	switch(index.column()) {
		case 0:
			return job->fname;
		case 1:
			return (int)job->type;
		case 2:
			return job->size;
		case 3:
			return QString::number(job->seeds_count_keep) + "/" + QString::number(job->seeds.size());
		case 4:
			return QString::number(job->chunks_done) + "/" + QString::number(job->chunks_count);
		case 5:
			return job->hash.toHex();
		default:
			return QString("Hmmm");
	}
}

QModelIndex JobsModel::index(int row, int column, const QModelIndex &parent) const
{
	if (!hasIndex(row, column, parent))
		return QModelIndex();

	 if (parent.isValid())
		 return QModelIndex();

	Job * job = jobs_[row];

	return createIndex(row, column, job);
}

QModelIndex JobsModel::parent(const QModelIndex &index) const {
	Q_UNUSED(index);
	return QModelIndex();
}

Qt::ItemFlags JobsModel::flags(const QModelIndex &index) const
{
	if (!index.isValid())
		return 0;

	return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QVariant JobsModel::headerData(int section, Qt::Orientation orientation, int role) const
{
	if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
		switch (section) {
			case 0:
				return _("Nazwa pliku");
			case 1:
				return _("Typ");
			case 2:
				return _("Rozmiar");
			case 3:
				return _("Ilość dawców");
			case 4:
				return _("Pobrano");
			case 5:
				return _("MD5");
			default:
				return "";
		}
	}

	return QVariant();
}


/*
	Model
*/

Model::Model() : mutex(1) {
	/// @todo implement this
	QFile data_("client_info.dat");
	data_.open(QIODevice::ReadOnly);

	QDataStream dataStream(&data_);
	dataStream >> client_id;

	int count;
	dataStream >> count;

	SharedFileDesc * file;
	for (int i = 0; i < count; ++i) {
		file = new SharedFileDesc();
		dataStream >> file->fname;
		dataStream >> file->path;
		dataStream >> file->hash;
		dataStream >> file->size;
		int t;
		dataStream >> t;
		file->type = (Common::FileType)t;
		shared_.push_back(file);
	}

	data_.close();
/*
        data_.setFileName("jobs_info.dat");
        data_.open(QIODevice::ReadOnly);

        dataStream.setDevice(&data_);
        dataStream >> count;

        int type;
        Job *job;
        for(int i = 0; i < count; ++i){
            job = new Job();
            dataStream >> job->fname;
            dataStream >> job->hash;
            dataStream >> job->size;
            dataStream >> type;
            job->type = (Common::FileType)type;
            dataStream >> job->chunk_size;
            dataStream >> job->chunks_done;
            dataStream >> job->chunks_count;
//            dataStream >> job->status;
            jobs_.push_back(job);

        }

        data_.close();
*/

}

Model::~Model() {

	foreach(Job * job, jobs_)
		delete job;

	foreach(SharedFileDesc * file, shared_)
		delete file;
}

void Model::saveAll() {
	QFile data_("client_info.dat");
	data_.open(QIODevice::WriteOnly);

	QDataStream dataStream(&data_);
	dataStream << client_id;
	dataStream << shared_.size();
	foreach(SharedFileDesc * file, shared_)
		dataStream << file->fname << file->path << file->hash << file->size << file->type;

	data_.close();

	/*
        data_.setFileName("jobs_info.dat");
        data_.open(QIODevice::WriteOnly);

        dataStream.setDevice(&data_);
       // dataStream << client_id;
        dataStream << jobs_.size();
        foreach(Job *job, jobs_)
            dataStream << job->fname << job->hash << job->size << job->type << job->chunk_size << job->chunks_done << job->chunks_count;// << job->status;

        data_.close();
*/
}

QByteArray Model::getChunk(QByteArray hash, int size, int chunk) {
	QByteArray ret;

	mutex.p();

	foreach (SharedFileDesc * file, shared_) {

		// znaleziono plik i jest to ten, którego szukamy
		if ( (file->hash == hash) && (file->size == size) ) {


			int chunk_size = 1024*16;

			int chunk_count = file->size / chunk_size;

			if (size % chunk_size)
				chunk_count++;

			// żądanie fragmentu o złym indeksie
			if ( (chunk >= chunk_count) || (chunk < 0) )
				break;

			// indeks się zgadza, więc odczytujemy fragment z pliku
			QFile f(file->path);
			f.open(QFile::ReadOnly);
			f.seek(chunk_size * chunk);
			ret = f.read(chunk_size);

			// jeśli odczyt się nie powiódł (odczytano zbyt mało danych)
			if ( (ret.size() != chunk_size) && (chunk != chunk_count-1) )
				break;

			f.close();
			break;
		}
	}

	mutex.v();
	return ret;
}

bool Model::setChunk(QByteArray hash, int size, int chunk, QByteArray chunk_data, int seed_id) {
	ScopedWriter sw("Model::setChunk()", true);

	foreach (Job * job, jobs_) {
		// znaleziono zadanie pobierania danego pliku
		if ( (job->hash == hash) && (job->size == size) ) {

			// zbyt wysoki numer fragmentu
			if (chunk >= job->chunks_count) {
				sw << "Wysłany fragment ma zbyt wysoki indeks. Ignoruję...\n";
				break;
			}

			return job->addChunk(chunk, chunk_data, seed_id);
		}
	}

	return false;
}

void Model::freeSeed(QByteArray hash, int size, int client_id) {
	foreach (Job * job, jobs_) {
		if ( (job->hash == hash) && (job->size == size) ) {
			job->freeSeed(client_id);
		}
	}
}

void Model::addJob(QString fname, QByteArray hash, int size, Common::FileType type) {
	foreach (Job * job, jobs_) {
		if (job->hash == hash)
			return;
	}

	Job * job = new Job;
	job->fname = fname;
	job->size = size;
	job->type = type;
	job->hash = hash;

	job->chunk_size = 1024*16;
	job->chunks_count = size / job->chunk_size;
	if (size % job->chunk_size)
		job->chunks_count++;

	jobs_.push_back(job);
}

QList<Job*> & Model::jobs() {
	return jobs_;
}

SharedFileDesc * Model::addSharedFile(QString path) {
	SharedFileDesc * file = new SharedFileDesc;

	QFileInfo fi(path);

	file->path = fi.canonicalFilePath();
	file->fname = fi.fileName();
	file->size = fi.size();
	file->type = Common::ftAny;

	// obliczenie sumy kontrolnej
	file->hash = Utils::md5sum(path);

	shared_.push_back(file);

	return file;
}

QList<SharedFileDesc *> & Model::shared() {
	return shared_;
}

} // namespace Client
